#include <stdio.h>
#include <stdlib.h>

typedef enum { false, true } bool;

typedef int Vertex; /* 顶点编号类型 */
#define kMaxVertex 10  /* 最大顶点数   */
#define infinity (1e9)
typedef struct VertSetNode *VertexSet;
struct VertSetNode {
    bool *in; /* 顶点状态数组，true表示该顶点在解集里 */
    int n_verts; /* 顶点总个数 */
    int in_v; /* 当前解集中的顶点数 */
    int in_e; /* 当前解集覆盖的边数 */
    int f; /* 当前解的f值 */
};

typedef struct EdgeNode *Edges; /* 图中的边 */
struct EdgeNode {
    Vertex u, v; /* 边的两个端点 */
};

typedef struct EdgeSetNode *EdgeSet; /* 边集合 */
struct EdgeSetNode {
    Edges e; /* 存储边的数组 */
    int size; /* 集合规模 */
};

EdgeSet InitEdgeSet(int m) {
    /* 初始化一个空的边集合，最大容量为m */
    EdgeSet E;

    E = (EdgeSet)malloc(sizeof(struct EdgeSetNode));
    E->e = (Edges)malloc(sizeof(struct EdgeNode) * m);
    E->size = 0;
    return E;
}

VertexSet InitSet(int n) {
    /* 初始化一个空的解集 */
    VertexSet s;
    int i;

    s = (VertexSet)malloc(sizeof(struct VertSetNode));
    s->in = (bool *)malloc(sizeof(bool) * n);
    for (i = 0; i < n; i++) {
        s->in[i] = false;
    }
    s->n_verts = n;
    s->in_v = 0;
    s->in_e = 0;
    s->f = 0;

    return s;
}

int f(Vertex v, VertexSet s, EdgeSet E) {
    int e_cnt, n, m, i;
    Vertex u;

    n = s->in_v; /* 当前解s中收录的顶点数 */
    m = E->size; /* 图中的边数 */
    e_cnt = 0; /* 统计将v加入解集后新增覆盖的边数 */
    for (i = 0; i < m; i++) { /* 检查每一条边 */
        if (E->e[i].u == v || E->e[i].v == v) { /* 如果该边的一个端点是v */
            /* 令u为该边的另一个端点 */
            if (E->e[i].v == v) {
                u = E->e[i].u;
            } else {
                u = E->e[i].v;
            }
            if (s->in[u] == false) { /* 若另一端u不在解集中 */
                e_cnt++; /* 则收录v以后这条边将会被覆盖 */
            }
        }
    }
    /* 若将v收入解集，则解集中的边数为(s->in_e+e_cnt)，点数为(n+1) */
    return ((s->in_e + e_cnt) << 1) - (n + 1); /* 收录v以后得到的f值 */
}

/* 算法16-5：爬山算法 HillClimbing(f, init_solution, E) */
void HillClimbing(int (*f)(Vertex v, VertexSet s, EdgeSet E),
                  VertexSet init_solution, EdgeSet E) {
    VertexSet current_solution;
    Vertex v, next_solution;
    int df, max_df, n;

    n = init_solution->n_verts; /* 图中的顶点数 */
    current_solution = init_solution;
    while (true) {
        /* S = Neighbors(current_solution)即从未收录的顶点中任选一个顶点 */
        /* 加入current_solution的解集的集合 */
        max_df = -infinity;
        for (v = 0; v < n;
                v++) { /* 从顶点集合中找出可以新增的每个顶点 */
            if (current_solution->in[v] ==
                        false) { /* v有可能加入current_solution成为S中的解 */
                df = f(v, current_solution, E) - current_solution->f;
                if (df > max_df) { /* 在邻域内找使评分值增加最多的解next_solution */
                    max_df = df;
                    next_solution = v;
                }
            }
        }
        if (max_df <= 0) { /* 若当前解邻域内没有更好的解 */
            return; /* 则current_solution即局部最优解 */
        } else { /* 否则，将next_solution加入解集，进行下一步搜索 */
            current_solution->in[next_solution] = true; /* next_solution加入 */
            current_solution->in_v++; /* 解集顶点数加 1 */
            current_solution->f += max_df; /* 更新当前解的f值 */
            /* 根据顶点数和f值反推当前解覆盖的边数，避免重复计算 */
            current_solution->in_e = (current_solution->f + current_solution->in_v) >> 1;
        }
    }
}
/* 算法16-5 结束 */

int main(void) {
    VertexSet solution;
    EdgeSet E;
    Vertex u, v;
    int n, m, i;

    scanf("%d %d", &n, &m);
    E = InitEdgeSet(m);
    for (i = 0; i < m; i++) {
        scanf("%d %d", &u, &v);
        E->e[E->size].u = u;
        E->e[E->size].v = v;
        E->size++;
    }
    solution = InitSet(n);
    HillClimbing(f, solution, E);
    for (v = 0; v < n; v++) {
        if (solution->in[v] == true) {
            printf("%d ", v);
        }
    }

    return 0;
}